package binding

import (
	"reflect"
	"strings"
	"sync"

	"github.com/segmentio/go-snakecase"
)

var structCache = newStructCacheMap()

const (
	ignore = "-"
	tagN   = "bind"

	posB = "b"
	posH = "h"
	posQ = "q"
	posE = "e"
)

type structCacheMap struct {
	m  map[reflect.Type]cachedStruct
	mu sync.Mutex
}

func newStructCacheMap() *structCacheMap {
	return &structCacheMap{m: map[reflect.Type]cachedStruct{}}
}

func (s *structCacheMap) get(rt reflect.Type) cachedStruct {
	if rt.Kind() == reflect.Ptr {
		rt = rt.Elem()
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	cs, find := s.m[rt]
	if !find {
		cs = s.parseStruct(rt)
		s.m[rt] = cs
	}
	return cs
}

func (s *structCacheMap) parseStruct(rt reflect.Type) cachedStruct {
	if rt.Kind() == reflect.Ptr {
		rt = rt.Elem()
	}

	var (
		numFields = rt.NumField()
		cs        = make(cachedStruct, 0, 4)

		rfs reflect.StructField
		tag string
	)

	for i := 0; i < numFields; i++ {
		rfs = rt.Field(i)
		tag = rfs.Tag.Get("bind")
		if tag == ignore {
			continue
		}
		var (
			binds  = splitTag(rfs.Tag.Get(tagN))
			commit = strings.TrimSpace(rfs.Tag.Get(tagN + ".comment"))
			defVal = strings.TrimSpace(rfs.Tag.Get(tagN + ".def"))
		)

		cf := cachedField{
			i:       i,
			rft:     rfs.Type,
			comment: commit,
			def:     defVal,
		}
		for _, bindKey := range binds {
			pn := cachedName{n: bindKey, p: posB}
			switch {
			case strings.HasPrefix(bindKey, "h."):
				pn.n = strings.TrimPrefix(bindKey, "h.")
				pn.p = posH
			case strings.HasPrefix(bindKey, "header."):
				pn.n = strings.TrimPrefix(bindKey, "header.")
				pn.p = posH
			case strings.HasPrefix(bindKey, "q."):
				pn.n = strings.TrimPrefix(bindKey, "q.")
				pn.p = posQ
			case strings.HasPrefix(bindKey, "query."):
				pn.n = strings.TrimPrefix(bindKey, "query.")
				pn.p = posQ
			case strings.HasPrefix(bindKey, "e."):
				pn.n = strings.TrimPrefix(bindKey, "e.")
				pn.p = posE
			case strings.HasPrefix(bindKey, "env."):
				pn.n = strings.TrimPrefix(bindKey, "env.")
				pn.p = posE
			case strings.HasPrefix(bindKey, "b."):
				pn.n = strings.TrimPrefix(bindKey, "b.")
				pn.p = posB
			case strings.HasPrefix(bindKey, "body."):
				pn.n = strings.TrimPrefix(bindKey, "body.")
				pn.p = posB
			}
			if pn.n == "" {
				pn.n = snakecase.Snakecase(rfs.Name)
				fpn := cachedName{n: rfs.Name, p: pn.p}
				cf.names = append(cf.names, fpn)
			}
			cf.names = append(cf.names, pn)
		}
		if len(cf.names) == 0 {
			cf.names = []cachedName{
				{n: snakecase.Snakecase(rfs.Name), p: posB},
				{n: rfs.Name, p: posB},
			}
		}
		cs = append(cs, cf)
	}

	return cs
}

type cachedStruct []cachedField

type cachedField struct {
	i       int          //索引
	rft     reflect.Type //类型
	names   []cachedName //绑定的位置和名称，[body, header, query]: name
	comment string       //注释
	def     string       //默认值
}

type cachedName struct {
	p string
	n string
}

func splitTag(tags ...string) []string {
	var ret []string
	for _, tag := range tags {
		arr := strings.Split(tag, ",")
		if len(arr) == 1 && arr[0] == "" {
			continue
		}
		for i, s := range arr {
			arr[i] = strings.TrimSpace(s)
		}
		if len(ret) == 0 {
			ret = arr
		} else {
			ret = append(ret, arr...)
		}
	}
	return ret
}
